import { Sequelize } from 'sequelize';
import type { Options } from 'sequelize';

const connections: { [key: string]: Sequelize } = {};

/**
 * @description 日志记录的函数
 * @param {string} msg - 日志内容, 通常为 sql 语句
 * @param {number} [executeTime] - 执行时间, 单位毫秒
 */
type ILoggerFn = (msg: string, executeTime?: number) => void;

type ILogger = boolean | ILoggerFn;

type IModelInitFns = ((sequelize: Sequelize, charset: string) => void)[];

type IConnectionItem = Options & {
  /** 通过 uri 连接数据库 */
  uri?: string;
  /**
   * 每一个连接项的日志记录
   * @default false
   */
  logger?: ILogger;
  /** 初始化模型的函数, 用于进行模型初始化 */
  modelInitFns?: IModelInitFns;
};

interface IConnection {
  [index: string]: IConnectionItem;
}

function getLogger(loggerOption?: ILogger) {
  if (loggerOption === true || loggerOption == null) {
    return console.log;
  } else if (typeof loggerOption === 'function') {
    return loggerOption;
  } else {
    return loggerOption;
  }
}

interface IConnectOption {
  /**
   * 日志记录, 如果不配置或false则不记录日志; 如果为 true 则使用 console.log 记录日志; 也可以是自定义的日志记录函数
   */
  logger?: ILogger;
  /** 数据库连接管理 */
  connections: IConnection;
}

function modelInit(sequelize: Sequelize, charset: string, initFns?: IModelInitFns) {
  // 模型初始化
  if (initFns instanceof Array) {
    for (let initItem of initFns) {
      if (typeof initItem === 'function') {
        initItem(sequelize, charset);
      }
    }
  }
}

async function connectToSequelize(option: IConnectionItem, logger?: ((msg: string) => void) | false) {
  // 链接地址
  let uri = option.uri;
  delete option['uri'];

  // 是否进行模型同步
  let sync = option.sync;
  delete option['sync'];

  // 日志输出响应时间
  if (option.benchmark == null) {
    option.benchmark = true;
  }

  // 连接参数
  let dialectOption: any = {};
  if (option.dialectOptions != null) {
    dialectOption = option.dialectOptions;
  }
  if (dialectOption.charset != null) {
    dialectOption.charset = 'utf8mb4';
  }
  option.dialectOptions = dialectOption;

  // 定义初始化的函数, 用于初始化模型
  let initModels = option.modelInitFns;
  delete option['modelInitFns'];

  let sequelize;
  if (uri != null) {
    sequelize = new Sequelize(uri, option);
  } else {
    sequelize = new Sequelize(option);
  }
  const db = sequelize.config.database;
  // 日志记录
  const connectLogger = logger || console.log;

  try {
    await sequelize.authenticate();
    connectLogger(`Connection '${db}' has been established successfully.`);
    // 初始化模型
    modelInit(sequelize, dialectOption.charset, initModels);
    // 同步模型, 慎用
    if (sync != null) {
      await sequelize.sync(sync);
    }
  } catch (error: any) {
    connectLogger(`Unable to connect to the database ${db}`);
    connectLogger(error.stack);
    console.error(error);
  }
  return sequelize;
}

/**
 * 数据库连接
 * @param option 连接配置
 */
export async function connect(option: IConnectOption) {
  const globalLogger = getLogger(option.logger);
  for (const app in option.connections) {
    if (Object.hasOwn(option.connections, app)) {
      const connection = option.connections[app];
      const logger = connection.logger ? getLogger(connection.logger) : globalLogger;
      connection.logging = logger;
      // 连接数据库
      connections[app] = await connectToSequelize(connection, globalLogger);
    }
  }
}

/**
 * 获取连接实例
 * @param name 连接名称, 默认为: default
 * @returns 数据库连接实例
 */
export function connection(name = 'default') {
  if (Object.hasOwn(connections, name)) {
    return connections[name];
  }
  return null;
}

/**
 * 关闭所有的数据库连接
 */
export async function closeAll() {
  for (const key in connections) {
    if (Object.hasOwn(connections, key)) {
      await connections[key].close();
      delete connections[key];
    }
  }
}

/**
 * 注册 Fastify 数据库连接
 * @param app Fastify 实例
 * @param options 连接配置
 */
export function register_fastify(app: any, options: IConnectionItem | IConnectOption) {
  let option: IConnectOption = {
    connections: {},
  };
  if (options.logger == null) {
    option.logger = (msg, executeTime) => app.log.debug(`${msg} [${executeTime || ''}ms]`);
  }
  // @ts-ignore
  if (options.connections == null) {
    option.connections = { default: options };
  } else {
    // @ts-ignore
    option.connections = options.connections;
  }
  // 添加应用退出监听, 释放资源
  app.addHook('onClose', async () => {
    await closeAll();
  });
  connect(option).then(); // 连接数据库
}
